package com.hydra.base.operate.log.aspect;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hydra.base.dto.auth.AuthUser;
import com.hydra.base.entity.IdEntity;
import com.hydra.base.error.BusinessException;
import com.hydra.base.feign.OpenFeignAuthService;
import com.hydra.base.operate.log.annotation.OperateLogInject;
import com.hydra.base.operate.log.annotation.OperateLogProperty;
import com.hydra.base.operate.log.common.DictDataConstant;
import com.hydra.base.operate.log.dto.CommonData;
import com.hydra.base.operate.log.entity.BaseOperateLogEntity;
import com.hydra.base.operate.log.enumeration.LogFieldType;
import com.hydra.base.resp.Response;
import com.hydra.base.services.BaseService;
import com.hydra.base.utils.FormatUtil;
import com.hydra.base.utils.JsonUtil;
import com.hydra.base.utils.RequestUtils;
import com.hydra.base.utils.SpringUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 操作日志记录切面
 * @author 陈煜敏
 * @date 2022/12/08
 */
@Slf4j
@Aspect
@Configuration
public class OperateLogAspect {

    @Resource
    private OpenFeignAuthService openFeignAuthService;

    /**
     * 使用切面进行记录操作日志
     *
     * 使用注意
     *  1.新增的接口必须返回code，不然会导致新增记录不到操作日志！
     */
    @Around("@annotation(com.hydra.base.operate.log.annotation.OperateLogInject)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Method method = this.getMethod(point);

        //获取注解
        OperateLogInject inject= method.getAnnotation(OperateLogInject.class);

        //获取服务类
        BaseService<IdEntity, BaseMapper<IdEntity>> service = this.getService(method);

        //获取入参实体
        IdEntity updateData = this.getUpdateData(point);
        //获取历史数据
        IdEntity historyData = this.getHistoryData(point);

        Object result = point.proceed(point.getArgs());

        //如果没启动日志，不进行比较且不保存日志结果了
        if(!service.enableOperateLog(updateData, historyData)){
            return result;
        }

        //两者进行对比，并保存结果
        BaseOperateLogEntity operateLogInfo = this.compare(updateData, historyData, inject, service);

        //记录操作日志
        log.info("操作日志：{}", JsonUtil.toJsonString(operateLogInfo));
        service.saveOperateLog(operateLogInfo, updateData, historyData);

        return result;
    }

    /**获取服务类对象*/
    private BaseService<IdEntity, BaseMapper<IdEntity>> getService(Method method){
        //获取service对象
        Class<? extends BaseService<IdEntity, BaseMapper<IdEntity>>> serviceClass = (Class<? extends BaseService<IdEntity, BaseMapper<IdEntity>>>)method.getDeclaringClass();
        return SpringUtils.getBean(serviceClass);
    }

    /**
     * 获取入参对象实体
     */
    private IdEntity getUpdateData(ProceedingJoinPoint point){
        Object[] args = point.getArgs();
        if(args.length == 0){
            throw new BusinessException("接口请求参数不能为空");
        }
        //这里有需要再视情况进行处理
        return (IdEntity)args[0];
    }

    /**
     * 获取历史对象实体
     */
    private IdEntity getHistoryData(ProceedingJoinPoint point){
        Object[] args = point.getArgs();
        if(args.length < 2){
            throw new BusinessException("接口请求参数不能为空");
        }
        //这里有需要再视情况进行处理
        return (IdEntity)args[1];
    }

    /**获取方法的数据*/
    private Method getMethod(ProceedingJoinPoint point){
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }


    /** 获取所有字段 */
    private List<Field> getFieldList(Class<?> clazz){
        if(clazz == null){
            return new ArrayList<>();
        }
        Field[] fields = clazz.getFields();
        Field[] declaredFields = clazz.getDeclaredFields();
        List<Field> result = new ArrayList<>();
        Collections.addAll(result, fields);
        Collections.addAll(result, declaredFields);
        //获取父类字段
        Class<?> superclass = clazz.getSuperclass();
        if(!"java.lang.Object".equals(superclass.getName())){
            List<Field> fieldList = this.getFieldList(superclass);
            result.addAll(fieldList);
        }

        return result;
    }

    /** 获取字段 */
    private FieldResult getFieldSplit(Object obj, String names) throws Exception{
        //判断是否需要获取属性
        String[] split = names.split("\\.");
        Object subFieldValue = obj;
        Field field = null;
        for (String p : split) {
            if(subFieldValue == null){
                break;
            }
            field = getField(subFieldValue, p);
            if(field != null){
                subFieldValue = field.get(subFieldValue);
            }
        }
        if(field == null){
            return null;
        }
        return new FieldResult(field, subFieldValue);
    }

    /** 获取字段 */
    private Field getField(Object obj, String name){
        if(obj == null){
            return null;
        }
        Class<?> aClass = obj.getClass();
        Field result = getField(aClass, name);
        if(result != null){
            result.setAccessible(true);
        }
        return result;
    }

    /** 获取字段 */
    private Field getField(Class<?> aClass, String name){
        Field result = null;
        try {
            result = aClass.getDeclaredField(name);
        } catch (Exception ignored) {
        }
        if(result == null){
            try {
                result = aClass.getField(name);
            } catch (Exception ignored) {
            }
        }
        //获取父类字段
        if(result == null){
            Class<?> superclass = aClass.getSuperclass();
            if(superclass != Object.class){
                return getField(superclass, name);
            }
        }
        return result;
    }


    /**获取属性名称*/
    private String getPropertyName(OperateLogProperty property){
        String propertyName = property.value();
        if(StringUtils.isBlank(propertyName)){
            propertyName = property.name();
        }
        return propertyName;
    }

    /**
     * 比较两个对象是否存在值
     */
    private BaseOperateLogEntity compare(IdEntity update, IdEntity history, OperateLogInject inject, BaseService<IdEntity, BaseMapper<IdEntity>> service) throws Exception {
        BaseOperateLogEntity logInfo = new BaseOperateLogEntity();
        if(history == null){//history为空，表示新增
            logInfo.setType("新增");
        }else{//编辑
            logInfo.setType("编辑");
        }
        //设置编码
        logInfo.setCode(this.getCode(update, inject, service));
        logInfo.setCate(inject.cate());
        //实体类的class
        List<String> logList = new ArrayList<>();
        //遍历，获取注解
        List<Field> fieldList = this.getFieldList(update.getClass());
        //获取注解、进行比较
        for (Field field : fieldList) {
            OperateLogProperty property = field.getAnnotation(OperateLogProperty.class);
            if(property == null){//没有配置注解的，跳过
                continue;
            }
            String logString = compareField(update, history, field);
            if(StringUtils.isNotBlank(logString)){
                logList.add(logString);
            }
        }
        String join = String.join(";</br>", logList);
        if(StringUtils.isNotBlank(join)){
            join += ";</br>";
        }
        logInfo.setInfo(join);

        AuthUser loggedUser = RequestUtils.getAuthUser();
        assert loggedUser != null;
        logInfo.setOperator(loggedUser.getCode());
        logInfo.setOperateTime(LocalDateTime.now());
        return logInfo;
    }

    /**
     * 比较字段并返回是否存在修改
     */
    private String compareField(Object update, Object history, Field field) throws Exception{
        //获取字段属性
        OperateLogProperty property = field.getAnnotation(OperateLogProperty.class);

        //获取字段的值，用于判断是否做了修改
        String name = field.getName();
        if(StringUtils.isNotBlank(property.property())){
            name = property.property();
        }
        Object newValue = this.getFieldValue(update, name, property);
        Object oldValue = this.getFieldValue(history, name, property);

        //配置的属性类型
        LogFieldType type = property.type();
        if(LogFieldType.List.equals(type)){
            //获取字段类型
            Class<?> fieldType = field.getType();
            if(String.class == fieldType){
                //处理字符串列表，就是以逗号形式隔开的字符串处理成列表
                List<CompareObject> newValueList = Arrays.stream(String.valueOf(newValue).split(",")).map(item->new CompareObject(item, item)).collect(Collectors.toList());
                List<CompareObject> oldValueList = Arrays.stream(String.valueOf(oldValue).split(",")).map(item->new CompareObject(item, item)).collect(Collectors.toList());
                String info = this.compareList(property, newValueList, oldValueList);
                if(StringUtils.isBlank(info)){
                    return null;
                }
                return this.getPropertyName(property) + "："+info;
            }else if(List.class == fieldType){
                // 处理列表, 此处如果配置了property
                if(StringUtils.isNotBlank(property.listProperty())){
                    String info = this.compareObjectList(newValue, oldValue, property);
                    if(StringUtils.isBlank(info)){
                        return null;
                    }
                    return this.getPropertyName(property) + "："+info;
                }else{
                    log.error("配置了列表属性但没有配置响应属性：{}", field.getName());
                }
            }
        }else{
            if(Objects.equals(newValue, oldValue)){
                return null;
            }
            return this.getPropertyName(property) + "：从【"+oldValue+"】改为【"+newValue+"】";
        }
        return null;
    }


    /**比较两个对象列表，抽取响应属性进行判断*/
    private String compareObjectList(Object newList, Object oldList, OperateLogProperty property){
        if(newList == null){
            newList = new ArrayList<>();
        }
        if(oldList == null){
            oldList = new ArrayList<>();
        }

        if(newList instanceof String){
            newList = Arrays.asList(((String) newList).split(","));
        }
        if(oldList instanceof String){
            oldList = Arrays.asList(((String) oldList).split(","));
        }
        List<Object> newValueList = (List<Object>) newList;
        List<Object> oilValueList = (List<Object>) oldList;

        Function<Object, CompareObject> mapper = item -> {
            try {
                String id = String.valueOf(getFieldValue(item, property.listIdProperty(), property));
                String name = String.valueOf(getFieldValue(item, property.listProperty(), property));

                if(StringUtils.isBlank(id)){
                    id = name;
                }
                return new CompareObject(id, name);
            } catch (Exception e) {
                return null;
            }
        };
        List<CompareObject> list1 = newValueList.stream().map(mapper).collect(Collectors.toList());
        List<CompareObject> list2 = oilValueList.stream().map(mapper).collect(Collectors.toList());
        return compareList(property, list1, list2);
    }

    /**比较两个字符串列表的值是否重复*/
    private String compareList(OperateLogProperty property, List<CompareObject> newList, List<CompareObject> oldList){
        //新数组里面找到旧数组没有的，就是新增
        List<String> addList = newList.stream()
                .filter(item -> !FormatUtil.isEmpty(item.getId()) && !oldList.stream().map(CompareObject::getId).collect(Collectors.toList()).contains(item.getId()))
                .map(CompareObject::getName)
                .collect(Collectors.toList());
        //旧数组里面找到新数组没有的，就是删除
        List<String> removeList = oldList.stream()
                .filter(item -> !FormatUtil.isEmpty(item.getId()) && !newList.stream().map(CompareObject::getId).collect(Collectors.toList()).contains(item.getId()))
                .map(CompareObject::getName)
                .collect(Collectors.toList());

        //判断是否配置了字典值
        if(StringUtils.isNotBlank(property.dict())){
            Map<String, String> dictDataMap = this.getDictDataMap(property.dict());
            addList = addList.stream().map(dictDataMap::get).collect(Collectors.toList());
            removeList = removeList.stream().map(dictDataMap::get).collect(Collectors.toList());
        }

        String add = !addList.isEmpty()?"新增【"+String.join("】【", addList)+"】":"";
        String remove = !removeList.isEmpty()?"删除【"+String.join("】【", removeList)+"】":"";
        String split = !FormatUtil.isEmpty(add) && !FormatUtil.isEmpty(remove) ?"," :"";
        return add + split + remove;
    }

    /**获取实体的code*/
    private String getCode(IdEntity data, OperateLogInject inject, BaseService<IdEntity, BaseMapper<IdEntity>> service){
        String operateLogCode = service.getOperateLogCode(data);
        if(StringUtils.isNotBlank(operateLogCode)){
            return operateLogCode;
        }

        String codeFieldStr = inject.codeField();
        Field codeField = this.getField(data, codeFieldStr);
        if(codeField == null){
            log.error("获取实体的code为空, {}", data);
            throw new BusinessException("获取实体的code值为空！");
        }
        try {
            Object code = codeField.get(data);
            if(code == null){
                return null;
            }
            return String.valueOf(code);
        } catch (IllegalAccessException e) {
            log.error("获取实体的code失败, {}", data);
            throw new BusinessException("获取实体的code值为空！", e);
        }
    }


    /**
     * 获取字段的值，用于判断是否做了修改
     */
    private Object getFieldValue(Object obj, String fieldName, OperateLogProperty property) throws Exception{
        if(obj == null){
            return "";
        }

        Object result;
        
        //获取字段名
        FieldResult fieldResult = this.getFieldSplit(obj, fieldName);
        if(fieldResult == null){
            return "";
        }

        Field field = fieldResult.getField();
        result = fieldResult.getValue();

        if(result == null){
            return "";
        }
        Class<?> type = field.getType();
        if(BigDecimal.class == type){
            result = ((BigDecimal)result).stripTrailingZeros().toPlainString();
        }

        //配置的属性类型
        LogFieldType propertyType = property.type();
        //如果不是List，按字典格式化数据
        if(!LogFieldType.List.equals(propertyType)){
            result = formatIfWithDictData(property, result);
        }

        return result;
    }

    /**
     * 如果配置字典值，替换字典值
     */
    private Object formatIfWithDictData(OperateLogProperty property, Object value){
        //判断是否配置了字典
        String dict = property.dict();
        if(StringUtils.isNotBlank(dict)){
            String code = String.valueOf(value);
            //获取字典数据
            String dictData = this.getDictData(dict, code);
            if(StringUtils.isNotBlank(dictData)){
                value = dictData;
            }
        }
        return value;
    }

    /**获取字典值数据*/
    private String getDictData(String dict, String code){
        if(StringUtils.isNotBlank(dict) && StringUtils.isNotBlank(code)){
            if(DictDataConstant.dictMap.containsKey(dict)){
                List<CommonData> dataList = DictDataConstant.dictMap.get(dict);
                Optional<CommonData> first = dataList.stream().filter(item -> code.equals(item.getCode())).findFirst();
                if(first.isPresent()){
                    return first.get().getName();
                }
            }
            Response<CommonData> dictDataResponse = openFeignAuthService.getDictDataByTypeCodeAndCode(dict, code);
            if(dictDataResponse.isSuccess() && dictDataResponse.getData() != null){
                CommonData data = dictDataResponse.getData();
                return data.getName();
            }
        }
        return null;
    }

    /**获取字典值数据集合*/
    private Map<String, String> getDictDataMap(String dict){
        if(StringUtils.isNotBlank(dict)){
            List<CommonData> dataList;
            if(DictDataConstant.dictMap.containsKey(dict)){
                dataList = DictDataConstant.dictMap.get(dict);
            }else{
                Response<List<CommonData>> response = openFeignAuthService.getDictDataByTypeCode(dict);
                if(!response.isSuccess()){
                    throw new BusinessException("获取字典值失败："+response.getMessage());
                }
                dataList = response.getData();
            }
            return dataList.stream().collect(Collectors.toMap(CommonData::getCode, CommonData::getName));
        }
        return new HashMap<>();
    }

    @Data
    @AllArgsConstructor
    private static class CompareObject {
        String id;
        String name;
    }

    @Data
    @AllArgsConstructor
    private static class FieldResult {
        Field field;
        Object value;
    }

}
